package cz.cuni.amis.episodic.dybanem;

import com.google.common.collect.Table;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.ArrayUtils;

/**
 *
 * @author ik
 */
public class ProbabilityUtils {

    public static List<Double> entropy(Table<Integer, String, Double> table) {
        List<Double> entropies = new ArrayList<>();
        for (int i = 0; i < table.rowMap().size(); i++) {
            // how ugly, isn't it? :-)
            entropies.add(entropy(
                    ArrayUtils.toPrimitive(table.rowMap().get(i).values().toArray(new Double[0]))));
        }
        return entropies;
    }

    /**
     * Computes entropy of one random variable.
     * @param distribution
     * @return
     */
    public static double entropy(double[] distribution) {
        double sum = 0;
        for (double p : distribution) {
            if(p!=0) {
            	sum += p * Math.log(p)/Math.log(distribution.length);
            }
        }
        return -sum;
    }
    
    /**
     * 
     * @param distributions first index is over random variables, second over outcomes of one variable
     * @return
     */
    public static double[] entropy(double[][] distributions) {
    	double[] entropy = new double[distributions.length];
    	for(int i = 0; i < entropy.length; i++) {
    		entropy[i] = entropy(distributions[i]);
    	}
    	return entropy;
    }

    public static double kullbackLiebrerDivergence(Double[] probP, Double[] probQ) {
        double kl = 0;
        for (int i = 0; i < probP.length; i++) {
            kl += probP[i] * Math.log(probP[i] / probQ[i]);
        }
        return kl;
    }

    public static double kullbackLiebrerDivergence(double[] probP, double[] probQ) {
        // TODO we use convex combination smoothing to deal with 0 probs, see http://mathoverflow.net/questions/72668/how-to-compute-kl-divergence-when-pmf-contains-0s
        final double alpha = 0.001;
        final double Ux = alpha/(double)probP.length;
        double kl = 0;
        double P, Q;
        for (int i = 0; i < probP.length; i++) {
            P = Ux + (1-alpha)*probP[i];
            Q = Ux + (1-alpha)*probQ[i];
            //if (probP[i] != 0) { // 0 * ln 0 = 0
            //  kl += probP[i] * Math.log(probP[i] / probQ[i]);
            if (P != 0) { // 0 * ln 0 = 0
                kl += P * Math.log(P / Q);
            }
        }
        if(kl < 0) return 0;
        return kl;
    }
}
